/*
 *  Copyright 2019-2023 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.zhengjie.modules.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alipay.service.schema.util.StringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import me.zhengjie.exception.BizException;
import me.zhengjie.modules.biz.domain.Vip;
import me.zhengjie.modules.biz.service.VipService;
import me.zhengjie.modules.order.domain.OrderDetail;
import me.zhengjie.modules.order.domain.PayOrder;
import me.zhengjie.modules.order.domain.dto.ProductDetailDTO;
import me.zhengjie.modules.order.domain.req.OrderDetailReq;
import me.zhengjie.modules.order.domain.req.RecordOrderReq;
import me.zhengjie.modules.order.domain.resp.OrderDetailResp;
import me.zhengjie.modules.order.domain.resp.OrderListResp;
import me.zhengjie.modules.order.domain.resp.OrderPreviewResp;
import me.zhengjie.modules.order.mapper.OrderDetailMapper;
import me.zhengjie.modules.order.service.OrderDetailService;
import me.zhengjie.modules.pay.domain.dto.ProductDTO;
import me.zhengjie.modules.pay.domain.req.BuyProductReq;
import me.zhengjie.modules.pay.enums.StatusEnum;
import me.zhengjie.modules.shop.domain.Product;
import me.zhengjie.modules.shop.domain.ProductCart;
import me.zhengjie.modules.shop.domain.ProductKind;
import me.zhengjie.modules.shop.mapper.ProductKindMapper;
import me.zhengjie.modules.shop.mapper.ProductMapper;
import me.zhengjie.modules.shop.service.ProductCartService;
import me.zhengjie.modules.shop.service.ProductKindService;
import me.zhengjie.modules.system.domain.AddressDetail;
import me.zhengjie.modules.system.mapper.AddressDetailMapper;
import me.zhengjie.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.zhengjie.modules.order.service.PayOrderService;
import me.zhengjie.modules.order.domain.vo.PayOrderQueryCriteria;
import me.zhengjie.modules.order.mapper.PayOrderMapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import me.zhengjie.utils.PageUtil;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.io.IOException;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;

import me.zhengjie.utils.PageResult;

import static me.zhengjie.modules.base.utils.TimeUtil.getExpireTime;
import static me.zhengjie.modules.base.utils.UnitConvertUtil.priceInYuan;
import static me.zhengjie.modules.biz.constant.GlobalConstant.*;
import static me.zhengjie.modules.biz.domain.enums.ErrorCode.*;
import static me.zhengjie.modules.biz.util.OrderSnCreate.createOrderSn;
import static me.zhengjie.modules.base.utils.UnitConvertUtil.priceInCents;
import static me.zhengjie.modules.pay.enums.StatusEnum.*;

/**
 * @description 订单服务实现
 * @author yiwei.he
 * @date 2024-03-29
 **/
@Service
@RequiredArgsConstructor
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements PayOrderService {

    private final PayOrderMapper payOrderMapper;
    private final ProductKindMapper productKindMapper;
    private final ProductKindService productKindService;
    private final OrderDetailService orderDetailService;
    private final OrderDetailMapper orderDetailMapper;
    private final ProductMapper productMapper;
    private final AddressDetailMapper addressDetailMapper;
    private final VipService vipService;
    private final ProductCartService productCartService;

    @Override
    public PageResult<PayOrder> queryAll(PayOrderQueryCriteria criteria, Page<Object> page){
        return PageUtil.toPage(payOrderMapper.findAll(criteria, page));
    }

    @Override
    public List<PayOrder> queryAll(PayOrderQueryCriteria criteria){
        return payOrderMapper.findAll(criteria);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(PayOrder resources) {
        save(resources);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(PayOrder resources) {
        PayOrder payOrder = getById(resources.getId());
        payOrder.copy(resources);
        saveOrUpdate(payOrder);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(List<Long> ids) {
        removeBatchByIds(ids);
    }

    @Override
    public void download(List<PayOrder> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (PayOrder payOrder : all) {
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("订单单号", payOrder.getOrderSn());
            map.put("交易状态，枚举值：SUCCESS：支付成功 REFUND：转入退款 NOTPAY：未支付 CLOSED：已关闭REVOKED：已撤销（仅付款码支付会返回）USERPAYING：用户支付中（仅付款码支付会返回）PAYERROR：支付失败（仅付款码支付会返回）", payOrder.getTradeState());
            map.put("微信后台生成的订单号", payOrder.getTransactionId());
            map.put("交易类型【交易类型，枚举值：JSAPI：公众号支付NATIVE：扫码支付 APP：APP支付 MICROPAY：付款码支付 MWEB：H5支付 FACEPAY：刷脸支付】", payOrder.getTradeType());
            map.put("总金额，默认为分", payOrder.getTotal());
            map.put("支付方式【1：微信支付】", payOrder.getPayment());
            map.put("订单创建时间", payOrder.getCreateTime());
            map.put("订单完成时间", payOrder.getSuccessTime());
            map.put("说明", payOrder.getRemark());
            map.put("用户id", payOrder.getUserId());
            map.put("商品id", payOrder.getPId());
            map.put("购买数量", payOrder.getNumber());
            map.put("订单类型【vip订单，商品订单】", payOrder.getOrderType());
            map.put("运费", payOrder.getFee());
            map.put("省/市/区", payOrder.getProvinceCityRegion());
            map.put("收货人姓名", payOrder.getRecName());
            map.put("收货人电话", payOrder.getRecPhone());
            map.put("收货人地址", payOrder.getRecAddress());
            map.put("快递公司编码", payOrder.getKdCode());
            map.put("快递公司名", payOrder.getKdName());
            map.put("快递单号", payOrder.getKdOrder());
            map.put("1-未售后 2-售后中 3-已售后", payOrder.getSoldStatus());
            map.put("订单过期时间", payOrder.getExpireTime());
            map.put("发货状态【0：未发货 1：已发货】", payOrder.getLogisticsStatus());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    @Transactional
    public Boolean confirm(PayOrderQueryCriteria criteria) throws BizException {
        if(criteria == null){
            throw new BizException(BODY_NOTNULL.getCode(),BODY_NOTNULL.getMessage());
        }
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>().eq("order_sn", criteria.getOrderSn()));
        if(payOrder == null){
            throw new BizException(ORDER_IS_NOT_EXIST.getCode(),ORDER_IS_NOT_EXIST.getMessage());
        }
        if(payOrder.getLogisticsStatus() == LOGISTICS_DEFAULT.getCode()){
            throw new BizException(MERCHANT_UNDELIVERED.getCode(),MERCHANT_UNDELIVERED.getMessage());
        }
        return this.update(new UpdateWrapper<PayOrder>().eq("order_sn",criteria.getOrderSn()).set("logistics_status",2));
    }

    @Override
    public Boolean record(RecordOrderReq req) {
        // logistics_status ： 1 已发货
        return this.update(new UpdateWrapper<PayOrder>()
                .eq("order_sn", req.getOrderSn())
                .set("kd_code", req.getKdCode())
                .set("kd_name", req.getKdName())
                .set("kd_order", req.getKdOrder())
                .set("fee", req.getFee())
                .set("logistics_status", 1)
        );
    }

    /**
     * 创建订单，扣库存
     * @param req
     * @return
     * @throws BizException
     */
    @Override
    @Transactional
    public String createOrder(BuyProductReq req) throws BizException {
        // 1.校验请求参数
        validateRequestParameters(req);
        // 2.是否是购物车中创建的订单
        checkIsCart(req);
        // 3.商品总数、总金额、商品名称
        BigDecimal totalPrice = BigDecimal.ZERO; // 购买商品总金额
        List<OrderDetail> orderDetails = new ArrayList<>(); // 需要保存的订单详情
        List<Long> kindIds = new ArrayList<>(); // 商品种类ids
        List<Long> productIds = new ArrayList<>();  // 商品ids
        Map<Long, ProductDTO> productDTOMap = new HashMap<>();  // kId,Object
        Map<Long, Product> productMap = new HashMap<>();        // pId,object
        List<ProductKind> updateStockList = new ArrayList<>(); // 需要商品库存的集合
        // 4.通过商品种类id批量查询商品种类的价格和封面
        List<ProductDTO> productList = req.getProductList();
        productList.forEach(productDTO -> {
            kindIds.add(productDTO.getKId());
            productIds.add(productDTO.getPId());
            productDTOMap.put(productDTO.getKId(), productDTO);
        });
        List<ProductKind> productKinds = productKindMapper.selectList(new QueryWrapper<ProductKind>().in("id", kindIds));
        List<Product> products = productMapper.selectList(new QueryWrapper<Product>().in("id", productIds));
        products.forEach( p -> productMap.put(p.getId(),p));
        // 5.遍历每一个商品种类，从map中取值查看需要扣除多少库存，并封装订单详情列表
        for (ProductKind kind : productKinds) {
            ProductDTO dto = productDTOMap.get(kind.getId());
            BigDecimal price = kind.getPrice().multiply(BigDecimal.valueOf(dto.getNum()));
            totalPrice = totalPrice.add(price);
            if (kind.getStock() < dto.getNum()) {
                throw new BizException(PRODUCT_ALREADY_SOLD.getCode(), PRODUCT_ALREADY_SOLD.getMessage());
            }
            kind.setStock(kind.getStock() - dto.getNum());
            kind.setSold(kind.getSold() + dto.getNum());
            // 封装订单详情对象
            OrderDetail detail = new OrderDetail();
            BeanUtil.copyProperties(dto, detail);
            detail.setPrice(kind.getPrice());
            detail.setTotalPrice(price);
            detail.setCover(productMap.get(dto.getPId()).getCover());
            orderDetails.add(detail);
            // 扣除商品的库存
            updateStockList.add(kind);
        }
        // 6.批量修改库存
        if (!updateStockList.isEmpty()) {
            productKindService.updateBatchById(updateStockList);
        }
        // 7.创建订单并且保存订单详情
        int total = priceInCents(totalPrice);
        int nums = orderDetails.stream().mapToInt(OrderDetail::getNum).sum(); // 购买商品总数
        return saveOrderDetail(req, nums, total, orderDetails);
    }

    /**
     * 购物车的订单 需要修改购物车该订单 状态
     * @param req
     * @throws BizException
     */
    private void checkIsCart(BuyProductReq req) throws BizException {
        if(req.getType() == CART_CREATE.getCode()){
            List<Long> cartIds = req.getCartIds(); // 获取需要更新状态的购物车id列表
            if (!cartIds.isEmpty()) {
                UpdateWrapper<ProductCart> updateWrapper = new UpdateWrapper<>();
                updateWrapper.in("id", cartIds).set("status", CART_AWAIT_DELETE.getCode());
                boolean updateResult = productCartService.update(updateWrapper);
                if (!updateResult) {
                    throw new BizException(UPDATE_CART_FAILED.getCode(), UPDATE_CART_FAILED.getMessage());
                }
            }else {
                throw new BizException(UPDATE_IDS_NOT_NULL.getCode(), UPDATE_IDS_NOT_NULL.getMessage());
            }
        }
    }

    @Override
    public OrderPreviewResp preview(BuyProductReq req) throws BizException {
        if(req == null || req.getProductList().isEmpty() || req.getUserId() == null){
            throw new BizException(BODY_NOTNULL.getCode(),BODY_NOTNULL.getMessage());
        }
        BigDecimal totalPrice = BigDecimal.ZERO; // 购买商品总金额
        // 设置商品详情信息
        OrderPreviewResp resp = new OrderPreviewResp();
        List<ProductDetailDTO> productDetailDTOS = new ArrayList<>();
        // 1.1购买的商品详情
        List<ProductDTO> productList = req.getProductList();
        // 1.2获取商品种类ids
        List<Long> kindIds = new ArrayList<>();
        List<Long> productIds = new ArrayList<>();
        Map<Long, Product> productMap = new HashMap<>();
        Map<Long, ProductKind> kindMap = new HashMap<>();
        // 1.2通过商品种类id批量查询商品种类的价格和封面
        productList.forEach(productDTO -> {
            kindIds.add(productDTO.getKId());
            productIds.add(productDTO.getPId());
        });
        List<Product> products = productMapper.selectList(new QueryWrapper<Product>().in("id", productIds));
        List<ProductKind> productKinds = productKindMapper.selectList(new QueryWrapper<ProductKind>().in("id", kindIds));
        products.forEach( p -> productMap.put(p.getId(),p));
        productKinds.forEach(kind -> kindMap.put(kind.getId(),kind));

        AddressDetail addressDetail = addressDetailMapper.selectOne(new QueryWrapper<AddressDetail>()
                .eq("user_id", req.getUserId())
                .eq("is_default", ADDRESS_DEFAULT.getCode()));
        if(addressDetail != null){
            BeanUtil.copyProperties(addressDetail,resp);
            resp.setName(addressDetail.getUsername());
        }
        for (ProductDTO dto : productList) {
            Product product = productMap.get(dto.getPId());
            ProductKind productKind = kindMap.get(dto.getKId());
            ProductDetailDTO detail = new ProductDetailDTO();
            detail.setCover(product.getCover());
            detail.setPId(product.getId());
            detail.setFee(product.getFee());
            detail.setName(product.getName());
            detail.setKId(productKind.getId());
            detail.setSpecification(productKind.getSpecification());
            detail.setPrice(productKind.getPrice());
            detail.setNum(dto.getNum());
            // 获取总金额
            totalPrice = totalPrice.add(detail.getPrice().multiply(BigDecimal.valueOf(dto.getNum())));
            productDetailDTOS.add(detail);
        }
        resp.setTotalPrice(totalPrice);
        resp.setProductDetails(productDetailDTOS);
        // 运费设置为0
        resp.setFee(BigDecimal.ZERO);
        return resp;
    }

    @Override
    public PageResult<OrderListResp> list(OrderDetailReq req, Page<Object> page) {
        // 封装响应参数
        List<OrderListResp> respList = new ArrayList<>();
        PayOrderQueryCriteria criteria = new PayOrderQueryCriteria();
        List<OrderListResp> orderList;
        // 设置请求参数
        setQryRequest(req, criteria, page);
        IPage<PayOrder> iPage = payOrderMapper.list(criteria, page);
        List<PayOrder> records = iPage.getRecords();
        records.forEach(record -> {
            // 封装返回结果
            getOrderList(record, respList);
        });
        if(req.getOrderStatus() == ALL_ORDER.getCode()){
            orderList = respList.stream()
                    .filter(resp -> resp.getOrderStatus() != null)
                    .collect(Collectors.toList());
        }else {
           orderList = respList.stream()
                    .filter(resp -> Objects.equals(resp.getOrderStatus(), req.getOrderStatus()))
                    .collect(Collectors.toList());
        }
        return PageUtil.toPage(orderList);
    }

    private static void setQryRequest(OrderDetailReq req, PayOrderQueryCriteria criteria,Page<Object> page) {
        // 不展示售后订单
        criteria.setSoldStatus(SOLD_DEFAULT.getCode());
        criteria.setUserId(req.getUserId());
        // 根据订单状态筛选查询条件，订单由多个状态影响，单独设置查询条件
        if(req.getOrderStatus() == ORDER_AWAIT_RECEIVE.getCode()){
            // 待收货 ： 1.未发货，2：已发货未收货
            criteria.setTradeState(SUCCESS.getCode());
        }
        if(req.getOrderStatus() == ORDER_SUCCESS.getCode()){
            criteria.setTradeState(SUCCESS.getCode());
            criteria.setLogisticsStatus(LOGISTICS_END.getCode());
        }
    }

    private void getOrderList(PayOrder record, List<OrderListResp> respList) {
        OrderListResp resp = new OrderListResp();
        BigDecimal totalPrice =  priceInYuan(Long.valueOf(record.getTotal()));
        List<OrderDetail> orderDetails;
        switch(record.getOrderType()){
            case SHOPPING_ORDER:
                // 正常订单
                orderDetails = orderDetailMapper.selectList(new QueryWrapper<OrderDetail>().eq("o_id", record.getId()));
                resp.setId(record.getId());
                resp.setOrderSn(record.getOrderSn());
                resp.setRealPrice(totalPrice);
                resp.setTotalPrice(totalPrice);
                resp.setCreateTime(record.getCreateTime());
                resp.setDetailList(orderDetails);
                // 设置订单状态
                setOrderStatus(record, resp);
                break;
            case VIP_ORDER:
                // vip订单
                orderDetails = new ArrayList<>();
                OrderDetail orderDetail = new OrderDetail();
                Vip vip = vipService.getById(record.getPId());
                orderDetail.setId(vip.getId());
                orderDetail.setName(vip.getName());
                // vip一次只能买一个
                orderDetail.setNum(1);
                orderDetail.setCover(VIP_IMG_URL);
                orderDetail.setPrice(vip.getPrice());
                orderDetails.add(orderDetail);
                resp.setOrderStatus(StatusEnum.ORDER_SUCCESS.getCode());
                resp.setId(record.getId());
                resp.setOrderSn(record.getOrderSn());
                resp.setRealPrice(totalPrice);
                resp.setTotalPrice(totalPrice);
                resp.setDetailList(orderDetails);
                resp.setCreateTime(record.getCreateTime());
                break;
        }
        respList.add(resp);
    }

    @Override
    public OrderDetailResp detail(String orderSn) throws BizException {
        if(StringUtils.isBlank(orderSn)){
            throw new BizException(ORDERSN_NOT_NULL.getCode(),ORDERSN_NOT_NULL.getMessage());
        }
        OrderDetailResp resp = new OrderDetailResp();
        // 1.获取订单信息
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>().eq("order_sn", orderSn));
        if(payOrder == null){
            throw new BizException(ORDER_IS_NOT_EXIST.getCode(),ORDER_IS_NOT_EXIST.getMessage());
        }
        BeanUtil.copyProperties(payOrder,resp);
        // 2.普通订单 or vip订单？
        // 2.1vip订单
        if(payOrder.getOrderType().equals(VIP_ORDER)){
            BigDecimal totalPrice = BigDecimal.valueOf(payOrder.getTotal()).divide(BigDecimal.valueOf(100));
            resp.setTotalPrice(totalPrice);
            resp.setRealPrice(totalPrice);
            return resp;
        }
        // 2.2 普通订单
        List<OrderDetail> detailList = orderDetailMapper.selectList(new QueryWrapper<OrderDetail>().eq("o_id", payOrder.getId()));
        // 获取商品的规格
        List<Long> kindIds = detailList.stream().map(OrderDetail::getKId).collect(Collectors.toList());
        List<ProductKind> productKinds = productKindMapper.selectList(new QueryWrapper<ProductKind>().in("id", kindIds));
        Map<Long, ProductKind> map = new HashMap<>();
        productKinds.forEach(kind -> map.put(kind.getId(),kind));
        detailList.forEach(detail ->{
            detail.setSpecification(map.get(detail.getKId()).getSpecification());
        });
        resp.setDetailList(detailList);
        BigDecimal totalPrice = BigDecimal.valueOf(payOrder.getTotal()).divide(BigDecimal.valueOf(100));
        resp.setTotalPrice(totalPrice);
        resp.setRealPrice(totalPrice);
        return resp;
    }

    private static void setOrderStatus(PayOrder record, OrderListResp resp) {
        Integer tradeState = record.getTradeState();
        Integer logisticsStatus = record.getLogisticsStatus();
        //  待付款: 已创建订单的商品 和 到支付页面取消支付的订单【定时任务10分钟会更新一次待支付订单（归还库存）】
        if(tradeState == StatusEnum.NOTPAY.getCode() || tradeState == ORDER_DEFAULT_STATUS.getCode()){
            resp.setOrderStatus(StatusEnum.ORDER_AWAIT_PAY.getCode());
            return;
        }
        // 待收货： 付款成功，但还未发货的商品为待收货订单
        if(tradeState == StatusEnum.SUCCESS.getCode() && logisticsStatus != StatusEnum.LOGISTICS_END.getCode()){
            resp.setOrderStatus(StatusEnum.ORDER_AWAIT_RECEIVE.getCode());
            return;
        }
        // 已完成： 付款成功，且已收货的订单为已完成订单【就算后续会售后，已完成订单列表仍然有改订单记录】
        if(tradeState == StatusEnum.SUCCESS.getCode()){
            resp.setOrderStatus(StatusEnum.ORDER_SUCCESS.getCode());
        }
    }

    private String saveOrderDetail(BuyProductReq req, int nums, int total, List<OrderDetail> orderDetails) {
        String orderSn = createOrderSn();
        PayOrder payOrder = new PayOrder();
        payOrder.setOrderSn(orderSn);
        payOrder.setUserId(req.getUserId());
        payOrder.setCreateTime(LocalDateTime.now());
        payOrder.setTradeState(StatusEnum.ORDER_DEFAULT_STATUS.getCode());
        payOrder.setNumber(nums);
        payOrder.setTotal(total);
        payOrder.setRecName(req.getName());
        payOrder.setRecPhone(req.getPhone());
        payOrder.setProvinceCityRegion(req.getProvinceCityRegion());
        payOrder.setRecAddress(req.getAddress());
        payOrder.setRemark(SHOPPING_ORDER);
        payOrder.setOrderType(SHOPPING_ORDER);
        // 订单有效时间设置到10分钟后，如果未进行操作，则订单过期【定时任务关闭】
        payOrder.setExpireTime(LocalDateTime.now().plusMinutes(10));
        this.save(payOrder);
        // 保存订单详情
        orderDetails.forEach(detail -> detail.setOId(payOrder.getId()));
        orderDetailService.saveBatch(orderDetails);
        return orderSn;
    }

    /**
     * 校验请求参数
     */
    private void validateRequestParameters(BuyProductReq req) throws BizException {
        if (StringUtil.isEmpty(req.getAddress()) || StringUtil.isEmpty(req.getName())
                || StringUtil.isEmpty(req.getPhone()) || StringUtil.isEmpty(req.getProvinceCityRegion())
                || Objects.isNull(req.getUserId())) {
            throw new BizException(BODY_NOTNULL.getCode(), BODY_NOTNULL.getMessage());
        }
    }


}